import { KV, getBy } from "../addons/kv";
import { EnvValue, getConfig } from "../models/config";
import { getStorage } from "../models/storage";

interface SlaveConfig {
  ssh: {
    privateKey: string;
    publicKey: string;
    server?: {
      port: number;
      users: Record<
        string,
        { allowedKeys: string[] } & (
          | { type: "root" }
          | { type: "forward"; allowedPorts: number[] }
        )
      >;
    };
  };
  forwards: Record<
    string,
    {
      from: { ip: string; port: number };
      to: { host: { ips: string[]; port: number }; port: number };
    }
  >;
  scripts: Record<string, { script: string }>;
  envs: Record<string, string | undefined>;
  crontabs: Record<
    string,
    { cron: string; script: string; envs: Record<string, EnvValue> }
  >;
}

async function getHost(kv: KV, id: string) {
  return getBy((await getConfig(kv)).hosts, "id", id);
}

async function getSshPort(kv: KV, hostId: string) {
  const host = await getHost(kv, hostId);
  if (host === undefined || !host.ssh.server.enabled) {
    return undefined;
  }
  if (host.ssh.server.port !== 0) {
    return host.ssh.server.port;
  }
  const storage = await getStorage(kv, hostId);
  if (storage?.ssh.state !== "running") {
    return undefined;
  }
  return storage.ssh.port;
}

export async function getSlaveConfig(
  kv: KV,
  id: string
): Promise<SlaveConfig | undefined> {
  const config = await getConfig(kv);
  const host = await getHost(kv, id);
  if (host === undefined) {
    return undefined;
  }

  const ssh: SlaveConfig["ssh"] = {
    privateKey: host.ssh.privateKey,
    publicKey: host.ssh.publicKey,
  };
  if (host.ssh.server.enabled) {
    ssh.server = {
      port: host.ssh.server.port,
      users: {},
    };
    ssh.server.users["root"] = {
      allowedKeys: [
        ...host.ssh.server.allowedKeys,
        ...config.default.host.ssh.server.allowedKeys,
      ],
      type: "root",
    };
  }

  const forwards: SlaveConfig["forwards"] = {};
  for (const { id, from, to } of config.forwards) {
    const toStorage = await getStorage(kv, to.host);
    if (toStorage?.ssh.state !== "running") {
      continue;
    }
    if (from.host === host.id) {
      if (from.type === "ssh") {
        const fromPort = await getSshPort(kv, from.host);
        if (fromPort === undefined) {
          continue;
        }
        forwards[id] = {
          from: { ip: "127.0.0.1", port: fromPort },
          to: {
            host: { ips: toStorage.ips, port: toStorage.ssh.port },
            port: to.port,
          },
        };
      } else if (from.type === "custom") {
        forwards[id] = {
          from: { ip: from.custom.ip, port: from.custom.port },
          to: {
            host: { ips: toStorage.ips, port: toStorage.ssh.port },
            port: to.port,
          },
        };
      }
    } else if (to.host === host.id) {
      if (host.ssh.server.enabled) {
        const publicKey = (await getHost(kv, from.host))?.ssh.publicKey;
        if (publicKey === undefined) {
          continue;
        }
        ssh.server!.users[from.host] = {
          allowedKeys: [publicKey],
          type: "forward",
          allowedPorts: [to.port],
        };
      }
    }
  }

  const envs: SlaveConfig["envs"] = {};
  for (const env of host.envs) {
    if (env.type === "inherit") {
      const value = getBy(config.default.host.envs, "key", env.key)?.value;
      if (value === undefined) {
        continue;
      }
      envs[env.key] = value;
    } else if (env.type === "custom") {
      envs[env.key] = env.custom;
    }
  }

  const scripts: SlaveConfig["scripts"] = {};
  const crontabs: SlaveConfig["crontabs"] = {};
  for (const crontab of host.crontabs) {
    if (!crontab.enabled) {
      continue;
    }
    const script = getBy(config.scripts, "id", crontab.script);
    if (script === undefined) {
      continue;
    }
    scripts[script.id] = { script: script.script };
    crontabs[crontab.id] = {
      cron: crontab.cron,
      script: crontab.script,
      envs: Object.fromEntries(
        crontab.envs.map(({ key, ...value }) => [key, value])
      ),
    };
  }

  return { ssh, forwards, scripts, envs, crontabs };
}
